<?php

/**
 * MyQEE ORM 核心类
 *
 * @author     jonwang(jonwang@myqee.com)
 * @category   MyQEE
 * @package    System
 * @subpackage Core
 * @copyright  Copyright (c) 2008-2011 myqee.com
 * @license    http://www.myqee.com/license.html
 */
class MyQEE_OOP_ORM_Data extends ArrayIterator
{

    /**
     * ID 字段名
     * @var string
     */
    protected $_id_field;

    /**
     * 当前类的名称
     * @var string
     */
    protected $_class_name;

    /**
     * 当前ORM的唯一ID
     * @var string
     */
    protected $_orm_result_uniqid_;

    /**
     * 当前ORM基本名称
     * @var string
     */
    protected $_orm_name;

    /**
     * 当前ORM对象
     * @var ORM
     */
    protected $_orm_object;

    /**
     * 是否已创建完成
     * 当$_orm_data_is_created=true时，修改对象值将被置于已修改对象
     * @var boolean
     */
    protected $_orm_data_is_created = false;

    /**
     * 数据是否修改过
     *
     * @var boolean
     */
    protected $_data_is_changed = false;

    /**
     * 记录已创建的字段
     * @example array('test'=>true,'test2'=>true,'field3'=>true);
     * @var array
     */
    protected $_created_offset = array();

    /**
     * 递增或递减值信息
     *
     * key为字段，不是offset
     *
     * @var array
     */
    protected $_value_increment = array();

    /**
     * 原始字段数据
     *
     * 在对象构造的时候会将每一个字段数据都设置进去，key和数据库字段对应而并非offset
     *
     * @var array
     */
    protected $_original_field_data = array();

    /**
     * 字段设置
     * @var array
     */
    protected static $_offset_setting = array();

    /**
     * 数据库字段和对象key的对应关系
     * array(
     *   'classname' => array(
     *   	'fieldname' => array(
     *   		'offset1',
     *   		'offset2',
     *   		...
     *   	),
     *   ),
     * )
     * @var array
     */
    protected static $_field_to_offset = array();

    /**
     * 记录所有ID字段
     *
     *   array(
     * 	     'classname' => 'id',
     *       'classname2' => 'mid',
     *   )
     *
     * @var array
     */
    protected static $_all_id_field_name = array();

    public function __construct($array = null)
    {
        $this->setFlags(ArrayIterator::ARRAY_AS_PROPS);

        $this->_class_name = get_class($this);
        $this->_renew_orm_config();

        if ( $array && is_array($array) ) $this->__orm_callback_ini_data_($array,true);
        $this->_orm_data_is_created = true;
    }

    /**
     * 构造对象时清除字段
     *
     * 销毁变量以便可以统一采用__get()方法获取
     */
    protected function _renew_orm_config()
    {
        $class_vars = get_class_vars($this->_class_name);
        if ( ! isset(OOP_ORM_Data::$_offset_setting[$this->_class_name]) )
        {
            OOP_ORM_Data::$_offset_setting[$this->_class_name] = array();
            OOP_ORM_Data::$_field_to_offset[$this->_class_name] = array();
            $create_new = true;
        }
        else
        {
            $create_new = false;
        }
        foreach ( $class_vars as $k => $v )
        {
            $kstr = strtolower($k[0]);
            if ( $kstr >= 'a' && $kstr <= 'z' )
            {
                if ( $create_new )
                {
                    OOP_ORM_Data::$_offset_setting[$this->_class_name][$k] = $v;
                }
                unset($this->$k);
            }
        }
        if ( $create_new )
        {
            OOP_ORM_Parse::check_config(OOP_ORM_Data::$_offset_setting[$this->_class_name], OOP_ORM_Data::$_field_to_offset[$this->_class_name], OOP_ORM_Data::$_all_id_field_name[$this->_class_name]);
        }
        $this->_id_field = OOP_ORM_Data::$_all_id_field_name[$this->_class_name];
    }

    public function __wakeup()
    {
        $this->__construct();
    }

    public function __call($method, $params)
    {
        # ORM 调用接口
        if ( substr($method, 0, 15) == '__orm_callback_' && method_exists($this, $method . '_') )
        {
            $count_params = count($params);
            $action_name = $method . '_';
            switch ( $count_params )
            {
                case 0 :
                    return $this->$action_name();
                    break;
                case 1 :
                    return $this->$action_name($params[0]);
                    break;
                case 2 :
                    return $this->$action_name($params[0], $params[1]);
                    break;
                case 3 :
                    return $this->$action_name($params[0], $params[1], $params[2]);
                    break;
                case 4 :
                    return $this->$action_name($params[0], $params[1], $params[2], $params[3]);
                    break;
                default :
                    return call_user_func_array(array($this, $action_name), $params);
                    break;
            }
        }
        return null;
    }

    public function offsetGet($index)
    {
        if ( !isset($this->_created_offset[$index]) )
        {
            # 构造数据
            $data = $this->_create_data($index);
        }
        else
        {
            $data = parent::offsetGet($index);
        }

        return $data;
    }

    public function offsetSet($index, $newval)
    {
        $config = $this->_get_offset_config($index);
        if ( ! $config )
        {
            # 没有相应的配置，直接快速设置后返回
            parent::offsetSet($index, $newval);
            return false;
        }

        $offset_exist = parent::offsetExists($index);

        if ( $this->_orm_data_is_created )
        {
            # 对象已构建完毕
            if ( $offset_exist && $config['is_readonly'] )
            {
                # 只读字段不允许修改
                return false;
            }
        }

        # 映射字段
        if ( isset($config['parent_field_name']) )
        {
            $parent_field = $config['parent_field_name'];
            $sub_field = $config['sub_field'];

            # 获取父数据
            $tmpdata = $this->$parent_field;
            if ( is_object($tmpdata) )
            {
                $tmpdata->$sub_field = $newval;
            }
            else
            {
                $tmpdata[$sub_field] = $newval;
            }

            # 设置数据
            if ( false === $this->offsetSet($parent_field, $tmpdata) )
            {
                # 若返回是false，则表示父类不允许设置（比如只读字段）
                return false;
            }
        }
        elseif ( isset($config['field_name']) )
        {
            if ( $this->_orm_data_is_created )
            {
                $this->_data_is_changed = true;
            }
            else
            {
                # 记录到旧数据
                $this->_original_field_data[$config['field_name']] = $newval;
            }
        }

        unset($this->_created_offset[$index]);

        parent::offsetSet($index, $newval);

        if ( isset($config['all_sub_fields']) && is_array($config['all_sub_fields']) )
        {
            foreach ($config['all_sub_fields'] as $sub_key)
            {
                # 清理掉以便可以重新构造获取数据
                unset($this->_created_offset[$sub_key]);
                parent::offsetUnset($sub_key);
            }
        }

        return true;
    }

    /**
     * 返回当前指针数据
     *
     * @see ArrayIterator::current()
     * @return fixed
     */
    public function current()
    {
        return $this->offsetGet($this->key());
    }

    public function offsetUnset($index)
    {
        unset($this->_created_offset[$index]);
        return parent::offsetUnset($index);
    }

    //TODO FOR 5.3
    public function unserialize($serialized)
    {
        $status = parent::unserialize($serialized);
        $this->__wakeup();
        return $status;
    }

    /**
     * getArrayCopy别名
     *
     * 注意，此方法将返回以对象键名为key的数组，且不会根据设置格式化数据<br>
     * 若希望获取以数据库字段为key的数据，可使用get_field_data()方法
     *
     * @return array
     */
    public function as_array()
    {
        return $this->getArrayCopy();
    }

    /**
     * 返回当前对象ID字段
     *
     * @return string
     */
    public function id_field_name()
    {
        return $this->_id_field;
    }

    /**
     * 获取指定key的配置
     *
     * @param string $index
     * @return array/null
     */
    protected function _get_offset_config($index)
    {
        if ( isset(OOP_ORM_Data::$_offset_setting[$this->_class_name][$index]) )
        {
            return OOP_ORM_Data::$_offset_setting[$this->_class_name][$index];
        }
        else
        {
            return null;
        }
    }

    /**
     * 根据字段名获取所有offset键名
     *
     * @param string $field_name
     * @return array
     */
    protected function _get_offset_by_field($field_name)
    {
        return (array)OOP_ORM_Data::$_field_to_offset[$this->_class_name][$field_name];
    }

    /**
     * 设置数据
     *
     * 键名需要是是对象的键名，而并非数据库字段的键名
     *
     * @param array $array
     */
    public function set_data(array $array)
    {
        if ( ! is_array($array) )
        {
            return false;
        }
        foreach ( $array as $k => $v )
        {
            $this->offsetSet($k, $v);
        }
    }

    /**
     * 指定offset递增
     *
     * 通过这个方法改变值后，构造SQL时会是`field_name`=`field_name`+1，而不是`field_name`=2这样，可解决并发问题
     *
     * @param string $offset
     * @param ing $value
     * @return $this
     */
    public function value_increment($offset, $value=1)
    {
        $this->$offset = $this->$offset + $value;

        $config = $this->_get_offset_config($offset);
        if ( ! $config ) continue;

        # 虚拟字段
        if ( $config['is_virtual_field'] ) continue;
        if ( ! isset($config['field_name']) ) continue;
        $field_name = $config['field_name'];

        $this->_value_increment[$field_name] = $value;

        return $this;
    }

    /**
     * 指定offset递减
     *
     * 与increment_value相反
     *
     * @param string $offset
     * @param ing $value
     * @return $this
     */
    public function value_decrement($offset, $value=1)
    {
        return $this->value_increment($offset,-$value);
    }

    /**
     * 更新数据
     *
     * @return int $status 作用的行数
     */
    public function update()
    {
        $data = $this->get_changed_data();
        if ( ! $data ) return 0;

        $orm = $this->orm();
        if ( ! method_exists($orm, 'update') )
        {
            Core::debug()->info('当前ORM对象' . get_class($orm) . '不支持update方法');
            return 0;
        }
        $id_field = $this->get_id_field();
        if ( $id_field )
        {
            $offset = $this->_get_offset_by_field($id_field);
            $offset = $offset[0];
            $where[$id_field] = $this->$offset;
        }
        else
        {
            throw new Exception('ORM:' . get_class($this) . '不存在ID字段，无法使用ORM系统自带的update方法更新。');
        }

        # 递增或递减数据处理
        if ( $this->_value_increment && method_exists($orm->driver(), 'value_increment') ) foreach ( $this->_value_increment as $field=>$value )
        {
            # 如果存在递增或递减的数据
            if ( isset($data[$field]) )
            {
                $orm->driver()->value_increment($field,$value);
                unset($data[$field]);
            }
        }

        $status = $orm->update($data, $where);

        $this->_clear_changed_value_setting();

        return $status;
    }

    /**
     * 插入数据
     *
     * @return array(插入ID,作用行数)
     */
    public function insert()
    {
        $orm = $this->orm();
        if ( ! method_exists($orm, 'insert') )
        {
            Core::debug()->info('当前ORM对象' . get_class($orm) . '不支持insert方法');
            return array(0, 0);
        }
        $status = $orm->insert( $this->get_changed_data() );
        if ( ! $status ) return array(0, 0);
        if ( $status[0] > 0 )
        {
            $id_field = $this->get_id_field();
            $offset = current( $this->_get_offset_by_field($id_field) );
            $this->$offset = $status[0];
        }

        $this->_clear_changed_value_setting();

        return $status;
    }

    /**
     * 删除对象数据
     * @return integer 操作行数
     * @throws Exception
     */
    public function delete()
    {
        $orm = $this->orm();
        $status = 0;
        $id_field = $this->get_id_field();
        if ( $id_field )
        {
            $where[$id_field] = $this->$id_field;
        }
        else
        {
            throw new Exception('ORM:' . get_class($this) . '不存在ID字段，无法使用ORM系统自带的delete方法删除。');
        }
        $status = $orm->delete($where);
        return $status;
    }

    /**
     * 销毁对象，以便释放内存
     * 不同于delete方法，destroy只是销毁数据在内存中的占用，而不对数据进行处理
     */
    public function destroy()
    {
        //TODO 暂时还不需要
    }

    /**
     * 获取以数据库字段名为键名的数组数据
     *
     * 会排除掉虚拟数据，此数据可直接用户数据库的update
     *
     * @return array
     */
    public function get_field_data()
    {
        # 获取全部字段数据
        $data = array();

        foreach ( array_keys(parent::getArrayCopy()) as $offset )
        {
            $this->_offset_value( $offset , $data , true);
        }

        # 其它没有构建过的数据，合并到老数据里
        $data = array_merge( $this->_original_field_data , $data );

        return $data;
    }

    /**
     * 获取修改的数据
     *
     * 注意，返回的数组的键名是字段的键名，而并不是对象的键名
     *
     * @return array
     */
    public function get_changed_data()
    {
        $data = array();

        foreach ( array_keys(parent::getArrayCopy()) as $offset )
        {
            $this->_offset_value( $offset , $data );
        }

        return $data;
    }

    /**
     * 获取指定$offset数据，并设置到$data相应的key中
     *
     * @param string $offset
     * @param array $data
     * @param boolean $is_all_data
     */
    protected function _offset_value( $offset , & $data , $is_all_data = false )
    {
        $config = $this->_get_offset_config($offset);
        if ( ! $config ) return;

        # 虚拟字段
        if ( $config['is_virtual_field'] ) return;
        if ( ! isset($config['field_name']) ) return;

        $field_name = $config['field_name'];

        if ( isset($data[$field_name]) )
        {
            # 已经有数据了
            return;
        }

        if ( isset($config['read_only']) && $config['read_only'] )
        {
            # 只读数据
            if ( $is_all_data )
            {
                $data[$field_name] = $this->_original_field_data[$field_name];
            }
            return;
        }

        $tmp_data = parent::offsetGet($offset);

        $is_object = is_object($tmp_data);
        # 只有是对象的数据才处理
        if ( $is_object )
        {
            # 调用是否修改过数据的回调方法
            if ( true==$is_all_data )
            {
                $is_change = true;
            }
            elseif ( isset($config['object']['callback']['is_change']) )
            {
                $run = $config['object']['callback']['is_change'];
                $is_change = $tmp_data->$run();
            }
            elseif ( $tmp_data instanceof OOP_ORM_Data )
            {
                $is_change = $tmp_data->__orm_callback_data_is_change();
            }
            else
            {
                return;
            }

            # 没有修改
            if ( ! $is_change ) return;

            $tmp_data = OOP_ORM_Parse::get_object_field_data($tmp_data, isset($config['object']['callback']['get_data']) ? $config['object']['callback']['get_data'] : null);
            if ( false === $tmp_data )
            {
                return;
            }
        }

        if ( true==$is_all_data || $tmp_data!==$this->_original_field_data[$field_name] )
        {
            if ( isset($config['format']) )
            {
                $tmp_data = OOP_ORM_Parse::format_data($tmp_data, $config['format']);
            }
            $data[$field_name] = $tmp_data;
        }
    }

    /**
     * 获取ID字段
     */
    public function get_id_field()
    {
        return $this->_id_field;
    }

    /**
     * 获取当前ORM
     *
     * @return OOP_ORM_Finder_DB
     * @return OOP_ORM_Finder_Cache
     * @return OOP_ORM_Finder_HttpGet
     */
    public function orm()
    {
        if ( ! $this->_orm_object )
        {
            if ( ! $this->_orm_name )
            {
                $tmpobj = $this;
                while ( $tmpobj )
                {
                    if ( is_object($tmpobj) )
                    {
                        $classname = get_class($tmpobj);
                    }
                    else
                    {
                        $classname = $tmpobj;
                    }
                    if ( preg_match('#^ORM_([a-z0-9_]+)_Data$#i', $classname, $m) )
                    {
                        $this->_orm_name = $m[1];
                        break;
                    }
                    else
                    {
                        $tmpobj = get_parent_class($tmpobj);
                    }
                }
                unset($tmpobj);
            }
            if ( ! $this->_orm_name )
            {
                throw new Exception('$this->_orm_name未定义');
            }
            $orm_class_name = 'ORM_' . $this->_orm_name . '_Finder';

            if ( ! class_exists($orm_class_name, true) )
            {
                throw new Exception('指定的ORM对象“' . $orm_class_name . '”不存在');
            }
            $this->_orm_object = new $orm_class_name();
        }
        return $this->_orm_object;
    }

    /**
     * 获取当前数据的组的所有数据
     *
     * @return array
     */
    public function get_group_data()
    {
        if ( ! $this->_orm_result_uniqid_ ) return null;

        return OOP_ORM_Result::get_group_data($this->_orm_result_uniqid_);
    }

    /**
     * 删除指定OFFSET的ORM的缓存（如果有）
     * @param string $index
     * @return boolean
     */
    public function delete_orm_cache( $index )
    {
        # 获取配置
        $config = $this->_get_offset_config($index);

        # 不存在指定的配置
        if ( ! $config ) return false;

        # 无缓存设置
        if ( !isset($config['orm']['cache']))return true;

        $c_config = $config['orm'];
        foreach ( $c_config['mapping'] as & $item )
        {
            $item = $this->$item;
        }
        return OOP_ORM_Parse::delete_orm_cache_data( $this, $index, $c_config );
    }

    /**
     * 删除指定的offset的缓存（如果有）
     *
     * @param string $index
     * @return boolean
     */
    public function delete_offset_cache( $index )
    {
        # 获取配置
        $config = $this->_get_offset_config($index);

        # 不存在指定的配置
        if ( ! $config ) return false;

        # 无缓存设置
        if ( !isset($config['cache']))return true;

        return OOP_ORM_Parse::delete_offset_cache_data( $this, $index, $config['cache'] );
    }

    /**
     * 获取一个原始数据
     * //TODO 还有待完善
     *
     * @param fixed $tmp_data
     */
    protected function _get_real_data( & $tmp_data )
    {
        if ( is_object($tmp_data) )
        {
            if ( $tmp_data instanceof OOP_ORM_Data || method_exists($tmp_data, 'getArrayCopy') )
            {
                if ( $tmp_data->__orm_callback_data_is_change() )
                {
                    $tmp_data = $tmp_data->getArrayCopy();
                }
            }
            elseif ( method_exists($tmp_data, '__toString') )
            {
                $tmp_data = (string)$tmp_data;
            }
        }
    }

    /**
     * 清理修改数据记录，以便再次执行$this->update()方法
     */
    protected function _clear_changed_value_setting()
    {
        $this->_data_is_changed = false;
        $this->_value_increment = array();
    }

    /**
     * 创建实例化数据
     *
     * @param string $key
     */
    protected function _create_data($index)
    {
        # 标记为已创建
        $this->_created_offset[$index] = true;

        $config = $this->_get_offset_config($index);
        if ( ! $config ) return false;

        if ( isset($config['cache']) )
        {
            # 数据缓存
            $data = OOP_ORM_Parse::get_offset_cache_data($this , $index , $config['cache']);
            if ( false!==$data )
            {
                parent::offsetSet($index, $data);
                return $data;
            }
        }

        if ( parent::offsetExists($index) )
        {
            $data = parent::offsetGet($index);
            if ( is_object($data) && $data instanceof stdClass )
            {
                # 需要转成array类型
                $data = (array)$data;
            }
        }
        elseif ( isset($config['parent_field_name']) )
        {
            $parent_field = $config['parent_field_name'];
            $sub_field = $config['sub_field'];
            # 映射字段
            $data = $this->$parent_field;
            if ( is_object($data) )
            {
                $data = $data->$sub_field;
            }
            else
            {
                $data = $data[$sub_field];
            }
        }
        elseif ( isset($config['orm']) )
        {
            # 获取数据
            $data = OOP_ORM_Parse::get_orm_data_by_config($this,$config['orm'], $index);
        }
        elseif ( isset($config['data']) )
        {
            # 处理mapping
            if ( isset($config['data']['mapping']) )
            {
                foreach ( $config['data']['mapping'] as $k => $v )
                {
                    $config['data']['where'][$v] = $this->$k;
                }
            }

            $data = false;
            # 处理缓存
            if ( isset($config['cache']) )
            {
                $data = OOP_ORM_Parse::get_cache_data($index, @$config['data']['where'], $config['cache']);
            }
            if ( false === $data )
            {
                # 获取数据
                $data = OOP_ORM_Parse::get_data($config['data']);

                # 缓存数据
                if ( isset($config['cache']) )
                {
                    OOP_ORM_Parse::set_cache_data($index, @$config['data']['where'], $config['cache'], $data);
                }
            }
        }
        else
        {
            $data = false;
        }

        // 需要返回OBJECT对象
        if ( isset($config['object']) )
        {
            $object_name = $config['object']['name'];
            if ( ! class_exists($object_name, true) )
            {
                throw new Exception('指定的对象:' . $object_name . '不存在！');
            }
            if ( isset($config['object']['callback']['set_data']) )
            {
                $obj = new $object_name();
                $call_set_data = $config['object']['callback']['set_data'];
                $obj->$call_set_data($data);
            }
            else
            {
                $obj = new $object_name($data);
            }
            unset($data); //释放引用关系
            $data = $obj;
        }
        parent::offsetSet($index, $data);

        # 设置缓存
        if ( false!==$data && isset($config['cache']) )
        {
            OOP_ORM_Parse::set_offset_cache_data($this , $index , $config['cache'] , $data);
        }

        return $data;
    }

    /**
     * 用于给ORM回调设置ORM对象
     */
    protected function __orm_callback_setorm_(OOP_ORM $orm)
    {
        $this->_orm_object = $orm;
    }

    /**
     * 用于给ORM回调设置数据
     */
    protected function __orm_callback_ini_data_($data = null, $is_field_key = false)
    {
        if ( ! is_array($data) ) return false;

        if ( $is_field_key )
        {
            # 传进来数据的key是数据表的字段名
            $tmpData = array();
            foreach ( $data as $k => $v )
            {
                if ( isset(OOP_ORM_Data::$_field_to_offset[$this->_class_name][$k]) )
                {
                    $field_name = OOP_ORM_Data::$_field_to_offset[$this->_class_name][$k][0];
                }
                else
                {
                    $field_name = $k;
                }
                $tmpData[$field_name] = $v;
            }
            unset($data);
            $data = $tmpData;
        }

        # 回调函数在ORM创建后调用设置数据
        $created = $this->_orm_data_is_created;
        $this->_orm_data_is_created = false;
        foreach ( $data as $key => $value )
        {
            $config = $this->_get_offset_config($key);
            if ( $config && isset($config['format']) )
            {
                # 反解格式化数据
                $value = OOP_ORM_Parse::de_format_data($value, $config['format']);
            }
            $this->offsetSet($key, $value);
        }
        $this->_orm_data_is_created = $created;
    }

    /**
     * 用于给ORM回调初始化数据
     */
    protected function __orm_callback_ini_result_(OOP_ORM_Result $result)
    {
        $this->_orm_result_uniqid_ = $result->get_uniqid();
    }

    /**
     * 用于给ORM回调判断是否修改过数据
     *
     * @return boolean
     */
    protected function __orm_callback_data_is_change_()
    {
        if ( $this->_data_is_changed ) return true;
        $data = array();
        foreach ( array_keys(parent::getArrayCopy()) as $offset )
        {
            $this->_offset_value( $offset , $data , false );
            if ( $data )return true;
        }
        return false;
    }
}